Skip to main content
Glama
+page.svelte8.95 kB
<script> import { page } from '$app/stores'; import { onMount } from 'svelte'; let design = null; let loading = true; let error = null; let linkedPrd = null; function formatDate(dateValue) { if (!dateValue) return '-'; try { let date; // ISO 문자열 형식인지 확인 (예: 2025-09-05T10:23:42.534Z) if (typeof dateValue === 'string' && dateValue.includes('T')) { date = new Date(dateValue); } // Unix timestamp 형식인지 확인 (예: 1757249412158.0) else if (typeof dateValue === 'string' && /^\d+\.?\d*$/.test(dateValue)) { date = new Date(parseFloat(dateValue)); } // 이미 숫자인 경우 else if (typeof dateValue === 'number') { date = new Date(dateValue); } // 기타 경우 직접 파싱 시도 else { date = new Date(dateValue); } // 유효한 날짜인지 확인 if (isNaN(date.getTime())) { return '-'; } // 날짜와 시간을 모두 표시 return date.toLocaleString('ko-KR', { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false }); } catch (error) { console.error('Date formatting error:', error, dateValue); return '-'; } } onMount(async () => { try { const response = await fetch(`/api/designs/${$page.params.id}`); if (response.ok) { design = await response.json(); // 연결된 요구사항이 있으면 해당 PRD 정보를 가져오기 if (design.requirement_id) { try { const prdResponse = await fetch(`/api/prds/${design.requirement_id}`); if (prdResponse.ok) { linkedPrd = await prdResponse.json(); } } catch (e) { console.error('연결된 PRD 정보 로드 실패:', e); } } } else { error = '설계를 찾을 수 없습니다'; } } catch (e) { error = '데이터를 불러오는 중 오류가 발생했습니다'; } finally { loading = false; } }); </script> <svelte:head> <title>{design?.title || '설계 상세보기'} - WorkflowMCP</title> </svelte:head> <div class="max-w-4xl mx-auto space-y-6"> <div class="flex items-center justify-between"> <div> <h1 class="text-3xl font-bold text-gray-900">설계 상세보기</h1> <p class="text-gray-600 mt-1">시스템 설계 문서 상세 정보</p> </div> <div class="flex space-x-3"> <a href="/designs" class="btn btn-secondary">← 목록으로</a> {#if design} <a href="/designs/{design.id}/edit" class="btn btn-primary">편집</a> {/if} </div> </div> {#if loading} <div class="flex justify-center py-12"> <div class="text-gray-500">데이터를 불러오는 중...</div> </div> {:else if error} <div class="bg-red-50 border border-red-200 rounded-md p-4"> <div class="text-red-800">{error}</div> </div> {:else if design} <div class="space-y-6"> <!-- 기본 정보 --> <div class="card"> <h2 class="text-xl font-semibold text-gray-900 mb-4">기본 정보</h2> <div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">제목</label> <div class="text-gray-900 font-medium">{design.title}</div> <div class="text-xs text-gray-500 mt-1 font-mono">ID: {design.id}</div> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">설계 타입</label> <span class="badge {design.design_type === 'system' ? 'bg-blue-100 text-blue-800' : design.design_type === 'architecture' ? 'bg-purple-100 text-purple-800' : design.design_type === 'ui_ux' ? 'bg-pink-100 text-pink-800' : design.design_type === 'database' ? 'bg-green-100 text-green-800' : 'bg-orange-100 text-orange-800'}"> {design.design_type === 'system' ? '시스템' : design.design_type === 'architecture' ? '아키텍처' : design.design_type === 'ui_ux' ? 'UI/UX' : design.design_type === 'database' ? '데이터베이스' : 'API'} </span> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">우선순위</label> <span class="badge {design.priority === 'high' ? 'bg-red-100 text-red-800' : design.priority === 'medium' ? 'bg-yellow-100 text-yellow-800' : 'bg-gray-100 text-gray-800'}"> {design.priority === 'high' ? '높음' : design.priority === 'medium' ? '보통' : '낮음'} </span> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">상태</label> <span class="badge {design.status === 'draft' ? 'bg-gray-100 text-gray-800' : design.status === 'review' ? 'bg-yellow-100 text-yellow-800' : design.status === 'approved' ? 'bg-green-100 text-green-800' : 'bg-blue-100 text-blue-800'}"> {design.status === 'draft' ? '초안' : design.status === 'review' ? '검토중' : design.status === 'approved' ? '승인됨' : '구현완료'} </span> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">생성일</label> <div class="text-gray-600">{formatDate(design.created_at)}</div> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">최종 수정일</label> <div class="text-gray-600">{formatDate(design.updated_at)}</div> </div> {#if design.requirement_id} <div> <label class="block text-sm font-medium text-gray-700 mb-1">연관 요구사항</label> {#if linkedPrd} <a href="/prds/{design.requirement_id}" class="text-blue-600 hover:text-blue-800 hover:underline font-medium" title="요구사항 상세보기" > {linkedPrd.title} </a> <div class="text-xs text-gray-500 mt-1">ID: {design.requirement_id}</div> {:else} <div class="text-gray-600">{design.requirement_id}</div> <div class="text-xs text-gray-400">(요구사항 정보 로드 실패)</div> {/if} </div> {/if} </div> {#if design.description} <div class="mt-4"> <label class="block text-sm font-medium text-gray-700 mb-1">설명</label> <div class="text-gray-900 whitespace-pre-wrap">{design.description}</div> </div> {/if} </div> <!-- 설계 상세사항 --> {#if design.design_details} <div class="card"> <h2 class="text-xl font-semibold text-gray-900 mb-4">설계 상세사항</h2> <div class="text-gray-900 whitespace-pre-wrap">{design.design_details}</div> </div> {/if} <!-- 다이어그램 --> {#if design.diagrams} <div class="card"> <h2 class="text-xl font-semibold text-gray-900 mb-4">다이어그램</h2> <div class="text-gray-900 whitespace-pre-wrap">{design.diagrams}</div> </div> {/if} <!-- 인수 조건 --> {#if design.acceptance_criteria} {@const criteria = (() => { try { if (typeof design.acceptance_criteria === 'string') { return JSON.parse(design.acceptance_criteria || '[]'); } else if (Array.isArray(design.acceptance_criteria)) { return design.acceptance_criteria; } else { return []; } } catch { return []; } })()} {#if criteria && criteria.length > 0} <div class="card"> <h2 class="text-xl font-semibold text-gray-900 mb-4">인수 조건</h2> <div class="space-y-3"> {#each criteria as criterion, index} <div class="flex items-start space-x-3 p-3 bg-gray-50 rounded-lg"> <span class="text-sm text-gray-500 font-medium min-w-0">{index + 1}.</span> <span class="text-sm text-gray-700">{criterion}</span> </div> {/each} </div> </div> {/if} {/if} <!-- 태그 --> {#if design.tags} {@const tags = (() => { try { if (typeof design.tags === 'string') { return JSON.parse(design.tags || '[]'); } else if (Array.isArray(design.tags)) { return design.tags; } else { return []; } } catch { return []; } })()} {#if tags && tags.length > 0} <div class="card"> <h2 class="text-xl font-semibold text-gray-900 mb-4">태그</h2> <div class="flex flex-wrap gap-2"> {#each tags as tag} <span class="badge bg-gray-100 text-gray-800">{tag}</span> {/each} </div> </div> {/if} {/if} <!-- 노트 --> {#if design.notes} <div class="card"> <h2 class="text-xl font-semibold text-gray-900 mb-4">노트</h2> <div class="text-gray-900 whitespace-pre-wrap">{design.notes}</div> </div> {/if} </div> {/if} </div> <style> .badge { @apply inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium; } .card { @apply bg-white rounded-lg shadow p-6; } .btn { @apply px-4 py-2 rounded-md font-medium transition-colors; } .btn-primary { @apply bg-blue-600 text-white hover:bg-blue-700; } .btn-secondary { @apply bg-gray-200 text-gray-900 hover:bg-gray-300; } </style>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/foswmine/workflow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server